/*-
 * SCTS.java        Service-Centre-Time-Stamp (TP-SCTS)
 * 
 * $LastChangedBy: stein $
 * $LastChangedRevision: 98 $
 * $LastChangedDate: 2007-02-20 15:18:26 +0000 (Tue, 20 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/SCTS.java $
 * 
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.SimpleTimeZone;

import com.googlecode.attention.util.Convert;


/**
 * TP-Service-Centre-Time-Stamp (TP-SCTS).
 * <BR>
 * <BR>The TP-Service-Centre-Time-Stamp field is given in semi-octet
 * representation, and represents the local time in the following way:
 * <BR>
 * <TABLE BORDER="0">
 * <TR><TD></TD><TD>Year:</TD><TD>Month:</TD><TD>Day:</TD><TD>Hour:</TD><TD>Minute:</TD><TD>Second:</TD><TD>Time Zone</TD></TR>
 * <TR><TD>Digits:<BR>(Semi-octets)</TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD><TD><CENTER>2</CENTER></TD></TR>
 * </TABLE>
 * <BR>
 * <BR>The Time Zone indicates the difference, expressed in quarters of
 * an hour, between the local time and GMT. In the first of the two
 * semi-octets, the first bit (bit 3 of the seventh octet of the
 * TP-Service-Centre-Time-Stamp field) represents the algebraic sign of
 * this difference (0: positive, 1: negative).
 * <BR>
 * <BR>The Service-Centre-Time-Stamp, and any other times coded in this
 * format that are defined in the present document, represent the time local
 * to the sending entity.
 * <BR>
 * <BR>If the MS has knowledge of the local time zone, then any time received
 * (e.g. Service-Centre-Time-Stamp) at the MS may be displayed in the local
 * time rather than the time local to the sending entity. Messages shall be
 * stored as received without change to any time contained therein.
 * <BR>
 * <BR>The Time Zone code enables the receiver to calculate the equivalent
 * time in GMT from the other semi-octets in the Service-Centre-Time-Stamp,
 * or indicate the time zone (GMT, GMT+1H etc.), or perform other similar
 * calculations as required by the implementation. The value contained in the
 * Time Zone field must take into account daylight saving time, such that when
 * the sending entity changes from regular (winter) time to daylight saving
 * (summer) time, there is a change to the value in the Time Zone field, for
 * example in the UK the winter setting is 00000000 and the summer setting
 * is 01000000.
 * <BR>
 * <BR>If the MS receives a non-integer value in the SCTS, it shall assume
 * that the digit is set to 0 but shall store the entire field exactly as
 * received.
 *
 * <BR><A HREF="http://www.dreamfabric.com/sms/scts.html">
 *              http://www.dreamfabric.com/sms/scts.html</A>
 *
 * @author Stein Gunnar Bakkeby
 */
public class SCTS extends VP implements Comparable<SCTS> {
  /* Field values */
  
  /** Year */
  public static final int YEAR = 0;
  /** Month of the year */
  public static final int MONTH = 1;
  /** Day of the month */
  public static final int DAY = 2;
  /** Hour of the day on a 24 hour clock */
  public static final int HOUR = 3;
  /** Minute of the hour */
  public static final int MINUTE = 4;
  /** Second of the minute */
  public static final int SECOND = 5;
  /** Timezone */
  public static final int TIMEZONE = 6;
  
  private String pattern = "yyyy.MM.dd - hh:mm:ss [z]";
  
  /**
   * Creates a new time stamp using the systems time and date.
   */
  public SCTS() {
    super(new int[7], VPF.ABSOLUTE_FORMAT);
    set(Calendar.getInstance());
  }
  
  /**
   * Creates a new time stamp using the given calendar.
   * 
   * @param date The date in Calendar format
   */
  public SCTS(final Calendar date) {
    super(new int[7], VPF.ABSOLUTE_FORMAT);
    set(date);
  }
  
  /**
   * Constructor, takes a string as argument (time stamp).
   *
   * @param yymmddhhmmsstt String of integers with the format yymmddhhmmsstt
   *    (year, month, day, hour, minute, second, timezone)
   */
  public SCTS(final String yymmddhhmmsstt) {
    super(Convert.hexToIntArray(Convert.nibble(yymmddhhmmsstt)),
          VPF.ABSOLUTE_FORMAT);
  }
  
  /**
   * Constructor, takes a time stamp and a VPF as argument.
   * 
   * @param yymmddhhmmsstt String of integers with the format yymmddhhmmsstt
   * (year, month, day, hour, minute, second, timezone)
   * @param vpf The Validity-Period-Format (for SMS-SUBMIT)
   */
  public SCTS(final String yymmddhhmmsstt, final VPF vpf) {
    super(Convert.hexToIntArray(Convert.nibble(yymmddhhmmsstt)), vpf);
  }
  
  /**
   * Constructor, takes an int array as argument (time stamp).
   *
   * @param yymmddhhmmsstt Array of integers with the format yymmddhhmmsstt
   *    (year, month, day, hour, minute, second, timezone)
   */
  public SCTS(final int[] yymmddhhmmsstt) {
    super(Convert.nibble(yymmddhhmmsstt), VPF.ABSOLUTE_FORMAT); 
  }
  
  /**
   * Constructor, takes a time stamp and a VPF as argument.
   * 
   * @param yymmddhhmmsstt Array of integers with the format yymmddhhmmsstt
   * (year, month, day, hour, minute, second, timezone)
   * @param vpf The Validity-Period-Format (for SMS-SUBMIT)
   */
  public SCTS(final int[] yymmddhhmmsstt, final VPF vpf) {
    super(Convert.nibble(yymmddhhmmsstt), vpf);
  }
  
  /**
   * This will return the time stamp in the format yyyy.mm.dd - hh:mm:ss
   * (year, month, day, hour, minute, second, timezone).
   * 
   * @return The time stamp
   */
  public String getTimeStamp() {
    return this.toString();
  }
  
  /**
   * Returns the timestamp field as a string, format: "yymmddhhmmsstt". 
   */
  public String getField() {
    final StringBuilder sb = new StringBuilder();
    sb.append(Convert.addZeros(Integer.toString(get(YEAR)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(MONTH)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(DAY)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(HOUR)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(MINUTE)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(SECOND)), 2));
    sb.append(Convert.addZeros(Integer.toString(get(TIMEZONE)), 2));
    return Convert.nibble(sb.toString());
  }

  /**
   * Returns a part of the time stamp defined by a given type.
   *
   * @param type See static field values
   * @return Integer representing time
   */
  public int get(final int type) {
    if (type < YEAR || type > TIMEZONE) return 0;
    if (type == TIMEZONE) {
      return octets[type];
    }
    return Integer.parseInt(Integer.toHexString(octets[type]));
  }
  
  /**
   * This will return a Calendar object representing the timestamp.
   * 
   * @return A Calendar representation of the SCTS
   */
  public Calendar getDate() {
    Calendar date = Calendar.getInstance();
    final int now = Calendar.getInstance().get(Calendar.YEAR);
    final int year = get(YEAR);
    int century = (now / 100) * 100;
    if ((century + year) > now + 20) century -= 100; 
    date.set(century + year,
             get(MONTH) - 1,      // due to Calendar month being 0-based
             get(DAY),
             get(HOUR),
             get(MINUTE),
             get(SECOND));
    int timezone = get(TIMEZONE);
    if (timezone >= 128) timezone = (timezone - 0x80) * -1; 
    date.setTimeZone(new SimpleTimeZone(timezone * 900000, "Unknown"));
    return date;
  }
  
  /**
   * Sets a part of the time stamp defined by a given type.
   * 
   * @param type See static field values
   * @param value int value, unchecked
   */
  public void set(final int type, final int value) {
    if (type >= YEAR && type <= TIMEZONE) {
      octets[type] = Integer.parseInt("" + value, 16);
    }
  }
  
  /**
   * This will set the time stamp to the given Calendar date
   * 
   * @param date Date in Calendar format
   */
  public void set(final Calendar date) {
    final int year = date.get(Calendar.YEAR);
    final int century = (year / 100) * 100;
    set(YEAR,     year - century);                  // year
    set(MONTH,    date.get(Calendar.MONTH) + 1);    // month
    set(DAY,      date.get(Calendar.DAY_OF_MONTH)); // day
    set(HOUR,     date.get(Calendar.HOUR_OF_DAY));  // hour
    set(MINUTE,   date.get(Calendar.MINUTE));       // minute
    set(SECOND,   date.get(Calendar.SECOND));       // second
    int timeZone = date.get(Calendar.ZONE_OFFSET) / 900000;
    if (timeZone < 0) {
      timeZone *= -1;
      timeZone |= 0x80;
    }
    set(TIMEZONE, timeZone);  // time zone
  }

  /**
   * Returns the time stamp (nibbled).
   *
   * @return Timestamp, format: yymmddhhmmsstt
   */
  public int[] getVP() {
    return Convert.nibble(octets);
  }
  
  /**
   * @return 20&lt;year&gt;.&lt;month&gt;.&lt;day&gt; &lt;hour&gt;:&lt;minute&gt;:&lt;second&gt; [&lt;timezone&gt;]
   */
  public String toString() {
    final SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    sdf.setTimeZone(getDate().getTimeZone());
    return sdf.format(getDate().getTime());
  }
  
  public void setSimpleDateFormatPattern(final String pattern) {
    this.pattern = pattern;
  }
  
  public void setSimpleDateFormat(final SimpleDateFormat dateFormat) {
    this.pattern = dateFormat.toPattern();
  }
  
  @Override
  public boolean equals(final Object another) {
    if (another instanceof SCTS) {
      final SCTS scts = (SCTS)another;
      if (octets.length == scts.octets.length) {
        for (int i = 0; i < octets.length; i++) {
          if (octets[i] != scts.octets[i]) {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }
  
  public int compareTo(final SCTS another) {
    int output = 0;
    for (int i = 0; i <= TIMEZONE; i++) {
      if (octets[i] == another.get(i)) continue;
      output = (octets[i] < another.get(i) ? -1 : 1);
      break;
    }
    return output;
  }
}